package MLB::Web::Game;

# Copyright 2008 Ted Romer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

use strict;
use warnings;

use Carp;
use Exporter::Lite;
use MLB::Web::Medium;
use MLB::Web::Team;
use Time::Local qw(timegm timegm_nocheck);

use base qw(Class::Accessor);

our @EXPORT = qw(game_from_id_summary_xml game_from_id_miniscoreboard_xml);

# status from miniscoreboard is generally one of 
# Preview 
# In Progress 
# Game Over  (we translate to Final)
# Final 
# Postponed

MLB::Web::Game->mk_ro_accessors
    (qw(
        id
        status
        type
        inning
        top_inning
        home_score
        away_score
        league
        series_code
        game_number
        start_time
        away
        home
        media
        ));

# Construct a game object from entry in gamesbydate.xml + gamedetail.xml
sub game_from_id_summary_xml {
    my $id = shift;
    my $summary = shift;
    my $xml = shift;

    croak("Must provide id, summary, and xml")
        if !defined($xml) || !defined($summary) || !defined($id);

    my ($y, $m, $d, $away_code, $home_code, $number) =
        $id =~ m/(^\d{4})_(\d{2})_(\d{2})_([[:alnum:]]+)_([[:alnum:]]+)_(\d+)/;

    if ( !defined($y) || !defined($m) || !defined ($d) ||
         !defined($away_code) || !defined($home_code) || !defined ($number) 
         ) {
        croak("id must have form <y>_<m>_<d>_<away>_<home>_<number>");
    }

    my $raw_media = $xml->{media} || [];
    my @media = ();
    for my $raw_medium ( @$raw_media ) {
        if ( $raw_medium->{type} eq 'audio' ) {
            push @media, medium_from_raw_medium($raw_medium);
        }
    }

    my ($hour, $min) = split(':',$summary->{start_time});
    if ( $hour == 12 ) {
        if ( $summary->{ampm} eq 'am' ) {
            $hour -= 12;
        }
    } else {
        if ( $summary->{ampm} eq 'pm' ) {
            $hour += 12;
        }
    }

    # Times are in US Eastern.
    
    # Compute the time assuming DST; use this to determine whether it is
    # really a DST time or not.  We rely on games not starting in the wee
    # hours on the day of the time change.
    my $approx_time = timegm_nocheck(0, $min, $hour + 4, $d, $m-1, $y-1900);
    
    my $offset = 5 - ( is_DST_US($approx_time)  ? 1 : 0 );
    my $start_time = timegm_nocheck(0, $min, $hour + $offset, $d, $m-1, $y-1900);
    return MLB::Web::Game->new
        (
         {
             id          => $id,
             status      => $summary->{status},
             type        => $summary->{type},
             home_score  => $summary->{'home_score'},
             away_score  => $summary->{'away_score'},
             league      => $summary->{league},
             series_code => $summary->{'series-code'},
             game_number => $summary->{'game-number'},
             start_time  => $start_time,
             away        => team_from_code($away_code),
             home        => team_from_code($home_code),
             media       => \@media,
         }
         );
}

# Construct a game object from entry in gamesbydate.xml + gamedetail.xml
sub game_from_id_miniscoreboard_xml {
    my $id = shift;
    my $summary = shift;
    my $xml = shift;

    croak("Must provide id, summary, and xml")
        if !defined($xml) || !defined($summary) || !defined($id);

    my ($y, $m, $d, $away_code, $home_code, $number) =
        $id =~ m/(^\d{4})_(\d{2})_(\d{2})_([[:alnum:]]+)_([[:alnum:]]+)_(\d+)/;

    if ( !defined($y) || !defined($m) || !defined ($d) ||
         !defined($away_code) || !defined($home_code) || !defined ($number) 
         ) {
        croak("id must have form <y>_<m>_<d>_<away>_<home>_<number>");
    }

    my $raw_media = $xml->{media} || [];
    my @media = ();
    for my $raw_medium ( @$raw_media ) {
        if ( $raw_medium->{type} eq 'audio' ) {
            push @media, medium_from_raw_medium($raw_medium);
        }
    }

    my ($hour, $min) = split(':',$summary->{time});
    if ( !$summary->{ampm} =~ m/[AP]M/i ) {
        warn "Unexpected ampm value " . $summary->{ampm};
    }

    if ( $hour == 12 ) {
        if ( $summary->{ampm} =~ m/AM/i ) {
            $hour -= 12;
        }
    } else {
        if ( $summary->{ampm} =~ m/PM/i ) {
            $hour += 12;
        }
    }

    # Times are always in US Eastern.
    my $time_zone = $summary->{time_zone};
    if ( defined($time_zone) && $time_zone ne 'ET' ) {
        warn "Unexpected time_zone $time_zone";
    }
    
    # Compute the time assuming DST; use this to determine whether it is
    # really a DST time or not.  We rely on games not starting in the wee
    # hours on the day of the time change.
    my $approx_time = timegm_nocheck(0, $min, $hour + 4, $d, $m-1, $y-1900);
    
    my $offset = 5 - ( is_DST_US($approx_time)  ? 1 : 0 );
    my $start_time = timegm_nocheck(0, $min, $hour + $offset, $d, $m-1, $y-1900);

    my $status =
        $summary->{status} =~ m/Game over/i ? 'Final' : $summary->{status};
    return MLB::Web::Game->new
        (
         {
             id          => $id,
             status      => $status,
             type        => $summary->{type},
             inning      => $summary->{inning},
             top_inning  => $summary->{'top_inning'},
             home_score  => $summary->{'home_team_runs'},
             away_score  => $summary->{'away_team_runs'},
             league      => $summary->{league},
             start_time  => $start_time,
             away        => team_from_code($away_code),
             home        => team_from_code($home_code),
             media       => \@media,
         }
         );
}

# Data from http://eclipse.gsfc.nasa.gov/SEhelp/daylightsaving.html

my $_US_DST = {
    2001 => {
        start => timegm(0, 0, 7,  1,  3, 101),
        end   => timegm(0, 0, 6, 28, 10, 101),
    },
    2002 => {
        start => timegm(0, 0, 7, 7, 3, 102),
        end   => timegm(0, 0, 6, 27, 9, 102),
    },
    2003 => {
        start => timegm(0, 0, 7, 6, 3, 103),
        end   => timegm(0, 0, 6, 26, 9, 103),
    },
    2004 => {
        start => timegm(0, 0, 7, 4, 3, 104),
        end   => timegm(0, 0, 6, 31, 9, 104),
    },
    2005 => {
        start => timegm(0, 0, 7, 3, 3, 105),
        end   => timegm(0, 0, 6, 30, 9, 105),
    },
    2006 => {
        start => timegm(0, 0, 7, 2, 3, 106),
        end   => timegm(0, 0, 6, 29, 9, 106),
    },
    2007 => {
        start => timegm(0, 0, 7, 11, 2, 107),
        end   => timegm(0, 0, 6, 4, 10, 107),
    },
    2008 => {
        start => timegm(0, 0, 7, 9, 2, 108),
        end   => timegm(0, 0, 6, 2, 10, 108),
    },
    2009 => {
        start => timegm(0, 0, 7, 8, 2, 109),
        end   => timegm(0, 0, 6, 1, 10, 109),
    },
    2010 => {
        start => timegm(0, 0, 7, 14, 2, 110),
        end   => timegm(0, 0, 6, 7, 10, 110),
    },
    2011 => {
        start => timegm(0, 0, 7, 13, 2, 111),
        end   => timegm(0, 0, 6, 6, 10, 111),
    },
    2012 => {
        start => timegm(0, 0, 7, 11, 2, 112),
        end   => timegm(0, 0, 6, 4, 10, 112),
    },
};

sub is_DST_US {
    my $time = shift;
    my @time = gmtime($time);
    my $year = $time[5];
    
    my $range = $_US_DST->{$year+1900};
    die "Missing US DST data for $year"
        if !defined($range);

    return $time >= $range->{start} && $time < $range->{end};
}

1;

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
